/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.awt.dnd;

import java.awt.Component;
import java.awt.Cursor;
import java.awt.Image;
import java.awt.Point;

import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;

import java.awt.dnd.peer.DragSourceContextPeer;

import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;

import java.util.TooManyListenersException;

/**
 * The <code>DragSourceContext</code> class is responsible for managing the
 * initiator side of the Drag and Drop protocol. In particular, it is responsible
 * for managing drag event notifications to the
 * {@linkplain DragSourceListener DragSourceListeners}
 * and {@linkplain DragSourceMotionListener DragSourceMotionListeners}, and providing the
 * {@link Transferable} representing the source data for the drag operation.
 * <p>
 * Note that the <code>DragSourceContext</code> itself
 * implements the <code>DragSourceListener</code> and
 * <code>DragSourceMotionListener</code> interfaces.
 * This is to allow the platform peer
 * (the {@link DragSourceContextPeer} instance)
 * created by the {@link DragSource} to notify
 * the <code>DragSourceContext</code> of
 * state changes in the ongoing operation. This allows the
 * <code>DragSourceContext</code> object to interpose
 * itself between the platform and the
 * listeners provided by the initiator of the drag operation.
 * <p>
 * <a name="defaultCursor"></a>
 * By default, {@code DragSourceContext} sets the cursor as appropriate
 * for the current state of the drag and drop operation. For example, if
 * the user has chosen {@linkplain DnDConstants#ACTION_MOVE the move action},
 * and the pointer is over a target that accepts
 * the move action, the default move cursor is shown. When
 * the pointer is over an area that does not accept the transfer,
 * the default "no drop" cursor is shown.
 * <p>
 * This default handling mechanism is disabled when a custom cursor is set
 * by the {@link #setCursor} method. When the default handling is disabled,
 * it becomes the responsibility
 * of the developer to keep the cursor up to date, by listening
 * to the {@code DragSource} events and calling the {@code setCursor()} method.
 * Alternatively, you can provide custom cursor behavior by providing
 * custom implementations of the {@code DragSource}
 * and the {@code DragSourceContext} classes.
 *
 * @see DragSourceListener
 * @see DragSourceMotionListener
 * @see DnDConstants
 * @since 1.2
 */

public class DragSourceContext
    implements DragSourceListener, DragSourceMotionListener, Serializable {

  private static final long serialVersionUID = -115407898692194719L;

  // used by updateCurrentCursor

  /**
   * An <code>int</code> used by updateCurrentCursor()
   * indicating that the <code>Cursor</code> should change
   * to the default (no drop) <code>Cursor</code>.
   */
  protected static final int DEFAULT = 0;

  /**
   * An <code>int</code> used by updateCurrentCursor()
   * indicating that the <code>Cursor</code>
   * has entered a <code>DropTarget</code>.
   */
  protected static final int ENTER = 1;

  /**
   * An <code>int</code> used by updateCurrentCursor()
   * indicating that the <code>Cursor</code> is
   * over a <code>DropTarget</code>.
   */
  protected static final int OVER = 2;

  /**
   * An <code>int</code> used by updateCurrentCursor()
   * indicating that the user operation has changed.
   */

  protected static final int CHANGED = 3;

  /**
   * Called from <code>DragSource</code>, this constructor creates a new
   * <code>DragSourceContext</code> given the
   * <code>DragSourceContextPeer</code> for this Drag, the
   * <code>DragGestureEvent</code> that triggered the Drag, the initial
   * <code>Cursor</code> to use for the Drag, an (optional)
   * <code>Image</code> to display while the Drag is taking place, the offset
   * of the <code>Image</code> origin from the hotspot at the instant of the
   * triggering event, the <code>Transferable</code> subject data, and the
   * <code>DragSourceListener</code> to use during the Drag and Drop
   * operation.
   * <br>
   * If <code>DragSourceContextPeer</code> is <code>null</code>
   * <code>NullPointerException</code> is thrown.
   * <br>
   * If <code>DragGestureEvent</code> is <code>null</code>
   * <code>NullPointerException</code> is thrown.
   * <br>
   * If <code>Cursor</code> is <code>null</code> no exception is thrown and
   * the default drag cursor behavior is activated for this drag operation.
   * <br>
   * If <code>Image</code> is <code>null</code> no exception is thrown.
   * <br>
   * If <code>Image</code> is not <code>null</code> and the offset is
   * <code>null</code> <code>NullPointerException</code> is thrown.
   * <br>
   * If <code>Transferable</code> is <code>null</code>
   * <code>NullPointerException</code> is thrown.
   * <br>
   * If <code>DragSourceListener</code> is <code>null</code> no exception
   * is thrown.
   *
   * @param dscp the <code>DragSourceContextPeer</code> for this drag
   * @param trigger the triggering event
   * @param dragCursor the initial {@code Cursor} for this drag operation or {@code null} for the
   * default cursor handling; see <a href="DragSourceContext.html#defaultCursor">class level
   * documentation</a> for more details on the cursor handling mechanism during drag and drop
   * @param dragImage the <code>Image</code> to drag (or <code>null</code>)
   * @param offset the offset of the image origin from the hotspot at the instant of the triggering
   * event
   * @param t the <code>Transferable</code>
   * @param dsl the <code>DragSourceListener</code>
   * @throws IllegalArgumentException if the <code>Component</code> associated with the trigger
   * event is <code>null</code>.
   * @throws IllegalArgumentException if the <code>DragSource</code> for the trigger event is
   * <code>null</code>.
   * @throws IllegalArgumentException if the drag action for the trigger event is
   * <code>DnDConstants.ACTION_NONE</code>.
   * @throws IllegalArgumentException if the source actions for the <code>DragGestureRecognizer</code>
   * associated with the trigger event are equal to <code>DnDConstants.ACTION_NONE</code>.
   * @throws NullPointerException if dscp, trigger, or t are null, or if dragImage is non-null and
   * offset is null
   */
  public DragSourceContext(DragSourceContextPeer dscp,
      DragGestureEvent trigger, Cursor dragCursor,
      Image dragImage, Point offset, Transferable t,
      DragSourceListener dsl) {
    if (dscp == null) {
      throw new NullPointerException("DragSourceContextPeer");
    }

    if (trigger == null) {
      throw new NullPointerException("Trigger");
    }

    if (trigger.getDragSource() == null) {
      throw new IllegalArgumentException("DragSource");
    }

    if (trigger.getComponent() == null) {
      throw new IllegalArgumentException("Component");
    }

    if (trigger.getSourceAsDragGestureRecognizer().getSourceActions() ==
        DnDConstants.ACTION_NONE) {
      throw new IllegalArgumentException("source actions");
    }

    if (trigger.getDragAction() == DnDConstants.ACTION_NONE) {
      throw new IllegalArgumentException("no drag action");
    }

    if (t == null) {
      throw new NullPointerException("Transferable");
    }

    if (dragImage != null && offset == null) {
      throw new NullPointerException("offset");
    }

    peer = dscp;
    this.trigger = trigger;
    cursor = dragCursor;
    transferable = t;
    listener = dsl;
    sourceActions =
        trigger.getSourceAsDragGestureRecognizer().getSourceActions();

    useCustomCursor = (dragCursor != null);

    updateCurrentCursor(trigger.getDragAction(), getSourceActions(), DEFAULT);
  }

  /**
   * Returns the <code>DragSource</code>
   * that instantiated this <code>DragSourceContext</code>.
   *
   * @return the <code>DragSource</code> that instantiated this <code>DragSourceContext</code>
   */

  public DragSource getDragSource() {
    return trigger.getDragSource();
  }

  /**
   * Returns the <code>Component</code> associated with this
   * <code>DragSourceContext</code>.
   *
   * @return the <code>Component</code> that started the drag
   */

  public Component getComponent() {
    return trigger.getComponent();
  }

  /**
   * Returns the <code>DragGestureEvent</code>
   * that initially triggered the drag.
   *
   * @return the Event that triggered the drag
   */

  public DragGestureEvent getTrigger() {
    return trigger;
  }

  /**
   * Returns a bitwise mask of <code>DnDConstants</code> that
   * represent the set of drop actions supported by the drag source for the
   * drag operation associated with this <code>DragSourceContext</code>.
   *
   * @return the drop actions supported by the drag source
   */
  public int getSourceActions() {
    return sourceActions;
  }

  /**
   * Sets the cursor for this drag operation to the specified
   * <code>Cursor</code>.  If the specified <code>Cursor</code>
   * is <code>null</code>, the default drag cursor behavior is
   * activated for this drag operation, otherwise it is deactivated.
   *
   * @param c the initial {@code Cursor} for this drag operation, or {@code null} for the default
   * cursor handling; see {@linkplain Cursor class level documentation} for more details on the
   * cursor handling during drag and drop
   */

  public synchronized void setCursor(Cursor c) {
    useCustomCursor = (c != null);
    setCursorImpl(c);
  }

  /**
   * Returns the current drag <code>Cursor</code>.
   * <P>
   *
   * @return the current drag <code>Cursor</code>
   */

  public Cursor getCursor() {
    return cursor;
  }

  /**
   * Add a <code>DragSourceListener</code> to this
   * <code>DragSourceContext</code> if one has not already been added.
   * If a <code>DragSourceListener</code> already exists,
   * this method throws a <code>TooManyListenersException</code>.
   * <P>
   *
   * @param dsl the <code>DragSourceListener</code> to add. Note that while <code>null</code> is not
   * prohibited, it is not acceptable as a parameter. <P>
   * @throws TooManyListenersException if a <code>DragSourceListener</code> has already been added
   */

  public synchronized void addDragSourceListener(DragSourceListener dsl)
      throws TooManyListenersException {
    if (dsl == null) {
      return;
    }

    if (equals(dsl)) {
      throw new IllegalArgumentException("DragSourceContext may not be its own listener");
    }

    if (listener != null) {
      throw new TooManyListenersException();
    } else {
      listener = dsl;
    }
  }

  /**
   * Removes the specified <code>DragSourceListener</code>
   * from  this <code>DragSourceContext</code>.
   *
   * @param dsl the <code>DragSourceListener</code> to remove; note that while <code>null</code> is
   * not prohibited, it is not acceptable as a parameter
   */

  public synchronized void removeDragSourceListener(DragSourceListener dsl) {
    if (listener != null && listener.equals(dsl)) {
      listener = null;
    } else {
      throw new IllegalArgumentException();
    }
  }

  /**
   * Notifies the peer that the <code>Transferable</code>'s
   * <code>DataFlavor</code>s have changed.
   */

  public void transferablesFlavorsChanged() {
    if (peer != null) {
      peer.transferablesFlavorsChanged();
    }
  }

  /**
   * Calls <code>dragEnter</code> on the
   * <code>DragSourceListener</code>s registered with this
   * <code>DragSourceContext</code> and with the associated
   * <code>DragSource</code>, and passes them the specified
   * <code>DragSourceDragEvent</code>.
   *
   * @param dsde the <code>DragSourceDragEvent</code>
   */
  public void dragEnter(DragSourceDragEvent dsde) {
    DragSourceListener dsl = listener;
    if (dsl != null) {
      dsl.dragEnter(dsde);
    }
    getDragSource().processDragEnter(dsde);

    updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), ENTER);
  }

  /**
   * Calls <code>dragOver</code> on the
   * <code>DragSourceListener</code>s registered with this
   * <code>DragSourceContext</code> and with the associated
   * <code>DragSource</code>, and passes them the specified
   * <code>DragSourceDragEvent</code>.
   *
   * @param dsde the <code>DragSourceDragEvent</code>
   */
  public void dragOver(DragSourceDragEvent dsde) {
    DragSourceListener dsl = listener;
    if (dsl != null) {
      dsl.dragOver(dsde);
    }
    getDragSource().processDragOver(dsde);

    updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), OVER);
  }

  /**
   * Calls <code>dragExit</code> on the
   * <code>DragSourceListener</code>s registered with this
   * <code>DragSourceContext</code> and with the associated
   * <code>DragSource</code>, and passes them the specified
   * <code>DragSourceEvent</code>.
   *
   * @param dse the <code>DragSourceEvent</code>
   */
  public void dragExit(DragSourceEvent dse) {
    DragSourceListener dsl = listener;
    if (dsl != null) {
      dsl.dragExit(dse);
    }
    getDragSource().processDragExit(dse);

    updateCurrentCursor(DnDConstants.ACTION_NONE, DnDConstants.ACTION_NONE, DEFAULT);
  }

  /**
   * Calls <code>dropActionChanged</code> on the
   * <code>DragSourceListener</code>s registered with this
   * <code>DragSourceContext</code> and with the associated
   * <code>DragSource</code>, and passes them the specified
   * <code>DragSourceDragEvent</code>.
   *
   * @param dsde the <code>DragSourceDragEvent</code>
   */
  public void dropActionChanged(DragSourceDragEvent dsde) {
    DragSourceListener dsl = listener;
    if (dsl != null) {
      dsl.dropActionChanged(dsde);
    }
    getDragSource().processDropActionChanged(dsde);

    updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), CHANGED);
  }

  /**
   * Calls <code>dragDropEnd</code> on the
   * <code>DragSourceListener</code>s registered with this
   * <code>DragSourceContext</code> and with the associated
   * <code>DragSource</code>, and passes them the specified
   * <code>DragSourceDropEvent</code>.
   *
   * @param dsde the <code>DragSourceDropEvent</code>
   */
  public void dragDropEnd(DragSourceDropEvent dsde) {
    DragSourceListener dsl = listener;
    if (dsl != null) {
      dsl.dragDropEnd(dsde);
    }
    getDragSource().processDragDropEnd(dsde);
  }

  /**
   * Calls <code>dragMouseMoved</code> on the
   * <code>DragSourceMotionListener</code>s registered with the
   * <code>DragSource</code> associated with this
   * <code>DragSourceContext</code>, and them passes the specified
   * <code>DragSourceDragEvent</code>.
   *
   * @param dsde the <code>DragSourceDragEvent</code>
   * @since 1.4
   */
  public void dragMouseMoved(DragSourceDragEvent dsde) {
    getDragSource().processDragMouseMoved(dsde);
  }

  /**
   * Returns the <code>Transferable</code> associated with
   * this <code>DragSourceContext</code>.
   *
   * @return the <code>Transferable</code>
   */
  public Transferable getTransferable() {
    return transferable;
  }

  /**
   * If the default drag cursor behavior is active, this method
   * sets the default drag cursor for the specified actions
   * supported by the drag source, the drop target action,
   * and status, otherwise this method does nothing.
   *
   * @param sourceAct the actions supported by the drag source
   * @param targetAct the drop target action
   * @param status one of the fields <code>DEFAULT</code>, <code>ENTER</code>, <code>OVER</code>,
   * <code>CHANGED</code>
   */

  protected synchronized void updateCurrentCursor(int sourceAct, int targetAct, int status) {

    // if the cursor has been previously set then don't do any defaults
    // processing.

    if (useCustomCursor) {
      return;
    }

    // do defaults processing

    Cursor c = null;

    switch (status) {
      default:
        targetAct = DnDConstants.ACTION_NONE;
      case ENTER:
      case OVER:
      case CHANGED:
        int ra = sourceAct & targetAct;

        if (ra == DnDConstants.ACTION_NONE) { // no drop possible
          if ((sourceAct & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK) {
            c = DragSource.DefaultLinkNoDrop;
          } else if ((sourceAct & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE) {
            c = DragSource.DefaultMoveNoDrop;
          } else {
            c = DragSource.DefaultCopyNoDrop;
          }
        } else { // drop possible
          if ((ra & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK) {
            c = DragSource.DefaultLinkDrop;
          } else if ((ra & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE) {
            c = DragSource.DefaultMoveDrop;
          } else {
            c = DragSource.DefaultCopyDrop;
          }
        }
    }

    setCursorImpl(c);
  }

  private void setCursorImpl(Cursor c) {
    if (cursor == null || !cursor.equals(c)) {
      cursor = c;
      if (peer != null) {
        peer.setCursor(cursor);
      }
    }
  }

  /**
   * Serializes this <code>DragSourceContext</code>. This method first
   * performs default serialization. Next, this object's
   * <code>Transferable</code> is written out if and only if it can be
   * serialized. If not, <code>null</code> is written instead. In this case,
   * a <code>DragSourceContext</code> created from the resulting deserialized
   * stream will contain a dummy <code>Transferable</code> which supports no
   * <code>DataFlavor</code>s. Finally, this object's
   * <code>DragSourceListener</code> is written out if and only if it can be
   * serialized. If not, <code>null</code> is written instead.
   *
   * @serialData The default serializable fields, in alphabetical order, followed by either a
   * <code>Transferable</code> instance, or <code>null</code>, followed by either a
   * <code>DragSourceListener</code> instance, or <code>null</code>.
   * @since 1.4
   */
  private void writeObject(ObjectOutputStream s) throws IOException {
    s.defaultWriteObject();

    s.writeObject(SerializationTester.test(transferable)
        ? transferable : null);
    s.writeObject(SerializationTester.test(listener)
        ? listener : null);
  }

  /**
   * Deserializes this <code>DragSourceContext</code>. This method first
   * performs default deserialization for all non-<code>transient</code>
   * fields. This object's <code>Transferable</code> and
   * <code>DragSourceListener</code> are then deserialized as well by using
   * the next two objects in the stream. If the resulting
   * <code>Transferable</code> is <code>null</code>, this object's
   * <code>Transferable</code> is set to a dummy <code>Transferable</code>
   * which supports no <code>DataFlavor</code>s.
   *
   * @since 1.4
   */
  private void readObject(ObjectInputStream s)
      throws ClassNotFoundException, IOException {
    ObjectInputStream.GetField f = s.readFields();

    DragGestureEvent newTrigger = (DragGestureEvent) f.get("trigger", null);
    if (newTrigger == null) {
      throw new InvalidObjectException("Null trigger");
    }
    if (newTrigger.getDragSource() == null) {
      throw new InvalidObjectException("Null DragSource");
    }
    if (newTrigger.getComponent() == null) {
      throw new InvalidObjectException("Null trigger component");
    }

    int newSourceActions = f.get("sourceActions", 0)
        & (DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK);
    if (newSourceActions == DnDConstants.ACTION_NONE) {
      throw new InvalidObjectException("Invalid source actions");
    }
    int triggerActions = newTrigger.getDragAction();
    if (triggerActions != DnDConstants.ACTION_COPY &&
        triggerActions != DnDConstants.ACTION_MOVE &&
        triggerActions != DnDConstants.ACTION_LINK) {
      throw new InvalidObjectException("No drag action");
    }
    trigger = newTrigger;

    cursor = (Cursor) f.get("cursor", null);
    useCustomCursor = f.get("useCustomCursor", false);
    sourceActions = newSourceActions;

    transferable = (Transferable) s.readObject();
    listener = (DragSourceListener) s.readObject();

    // Implementation assumes 'transferable' is never null.
    if (transferable == null) {
      if (emptyTransferable == null) {
        emptyTransferable = new Transferable() {
          public DataFlavor[] getTransferDataFlavors() {
            return new DataFlavor[0];
          }

          public boolean isDataFlavorSupported(DataFlavor flavor) {
            return false;
          }

          public Object getTransferData(DataFlavor flavor)
              throws UnsupportedFlavorException {
            throw new UnsupportedFlavorException(flavor);
          }
        };
      }
      transferable = emptyTransferable;
    }
  }

  private static Transferable emptyTransferable;

    /*
     * fields
     */

  private transient DragSourceContextPeer peer;

  /**
   * The event which triggered the start of the drag.
   *
   * @serial
   */
  private DragGestureEvent trigger;

  /**
   * The current drag cursor.
   *
   * @serial
   */
  private Cursor cursor;

  private transient Transferable transferable;

  private transient DragSourceListener listener;

  /**
   * <code>true</code> if the custom drag cursor is used instead of the
   * default one.
   *
   * @serial
   */
  private boolean useCustomCursor;

  /**
   * A bitwise mask of <code>DnDConstants</code> that represents the set of
   * drop actions supported by the drag source for the drag operation associated
   * with this <code>DragSourceContext.</code>
   *
   * @serial
   */
  private int sourceActions;
}
